[免杀学习]动态链接库初探(下)
这篇文章会详细的讲述动态链接库的运行时动态链接机制,如果没有基础的师傅建议先看上篇文章《 [免杀学习]动态链接库初探(下) 》
知识点回顾
上篇文章中主要讲解了动态链接类型里的加载时动态链接,与本节要讲的运行时动态链接的区别在:
理论层面
加载时动态链接是在程序编译时就把对应的DLL文件加载进了程序,而运行时动态链接则是在程序运行时,也就是代码执行到了 LoadLibrary这个函数时才会把对应的DLL文件加载进程序,然后处理一下才可以使用
这里的处理指的是运行时动态链接需要通过一些额外的步骤才能开始使用DLL中的函数,而加载时动态链接则不需要额外的步骤,在导入DLL后就可以直接用函数名称来使用函数
虽然看起来运行时动态链接比加载时动态链接和静态链接在使用和理解上要难一丢丢,但是在免杀方面有很大的优势,如
- 运行时动态链接可以让攻击者在需要的时候才加载和调用DLL函数,而不是在程序启动时就加载所有的DLL,这样可以减少可疑的行为和被检测的风险。
- 运行时动态链接可以让攻击者使用API函数来获取DLL函数的地址,而不是使用导入表或者重定位表,这样可以避免被静态分析工具发现和拦截
- 运行时动态链接可以让攻击者使用反射技术来加载和执行DLL文件,而不是使用系统提供的加载器,这样可以绕过一些安全机制和监控工具。、
- 运行时动态链接不需要lib倒入库,只要能找到DLL即可利用
代码层面
加载时动态链接:
需要在代码中显示调用,并且需要手动导入lib导入库,或者说明查找路径
1 |
|
运行时动态链接:
不需要显示调用,但需要额外处理
1 | // 定义一个函数指针 |
说完了区别,开始系统的讲解一下运行时动态链接的编写和使用
基础知识
函数指针
函数指针顾名思义就是指向函数的指针
说的再本质一点就是指针中存储的地址是一个函数的地址
函数指针的基本定义格式为:
1 | //定义一个函数指针变量 |
看个例子来理解
1 |
|
从这个例子可以看出,这种声明方式相当于是声明了一个函数指针变量,这个变量适用于返回值为int,且有2个int参数的函数
但这种方式有个缺点,就是无法复用,如果我们有多个符合这种函数特征的方法需要用函数指针调用,那是不是还得多次声明多个函数指针变量?
为了解决这个问题,只需要在开头加个typedef,这样就相当于把funcPtr当做一个新的类型来使用,可以通过这个新的类型来声明函数指针变量
上述代码可以修改为
1 |
|
函数指针刚开始看可能会觉得有点抽象,但理解后其实很容易看透其本质
Windows Api编码问题
win的api函数一般会提供2个版本,一个是函数名+A,一个是函数名+W
函数名+A结尾:支持ANSI编码,也就是ASCII编码,也可以说是多字节字符集编码
函数名+W结尾:支持Unicode编码,也就是宽字节编码
在Visual studio2022中,使用的编码方式为ASCII编码,所以如果要使用W结尾的函数,那么必须在字符串型参数的双引号前加”L“,表明以Unicode编码这个字符串
当然,如果你不想在每个函数结尾都要手动声明A或者W的话,可以设置项目属性>配置类型->高级->字符集->多字节字符集,这样visual 就会默认使用A结尾的api函数
tips:如果需要用中文,那还是推荐使用Unicode编码,因为ansi对中文会乱码
LoadLibrary函数
要进行运行时的动态链接,通常顺序是引入DLL->查找DLL中的函数地址->使用函数->卸载DLL
LoadLibrary函数的作用就是引入DLL
1 | HMODULE LoadLibraryA( |
lpLibFileName:模块的名称,可以是DLL或EXE文件。如果指定了完整路径,只搜索该路径;如果指定了相对路径或没有路径的模块名,使用标准搜索策略。如果省略了文件扩展名,会追加默认的“.DLL”。
注释:LoadLibrary方法通常就是为了导入DLL模块,获取到DLL模块的句柄后就可以在后续获取其中想要使用的函数地址
在代码中通常是这样用的
1 | // 加载DLL |
HMODULE是一个模块句柄,相当于获得了dll的地址。它是一个指针类型
GetProcAddress函数
当我们引入一个DLL后,我们就可以使用其句柄来查找存在于这个DLL中的函数的地址了(第二步),这个过程的实现主要就是通过使用GetProcAddress函数
1 | FARPROC GetProcAddress( |
hModule:模块句柄
lpProcName:函数名
在代码中这样使用
1 | typedef int (* func)(int,int); |
因为GetProcAddress的返回值是一个FARPROC类型,它是一个通用函数指针,没有指定参数和返回值的类型。为了能够正确地调用add函数,需要将FARPROC转换为func类型,也就是指定了参数和返回值为int的函数指针。这样才能保证函数调用的安全和正确。
因为需要进行类型转换,所以必须使用typedef来创建一个函数指针的类型,而不能只定义一个函数指针变量
FreeLibrary函数
1 | BOOL FreeLibrary( |
作用很简单,就是把导入的DLL卸载了
完整代码
首先把上篇生成的.dll文件放到当前项目下,然后在项目里新建一个cpp输入以下代码
1 |
|
成功运行
举一反三
我们在上篇说道,DLL库有DllMain入口函数,我们能否在加载DLL(LoadLibrary)或卸载DLL(FreeLibrary)时做一些恶意操作?
重新编写一下MyDll,在上篇我们已经演示过在引入DLL时执行额外操作,这次我们来尝试在卸载DLL时打开计算器
打开MyDll2.cpp,编写如下代码
1 | //MyDll2.cpp |
重新生成项目(这里有问题的建议看看上篇),然后只需要把生成的dll文件放在Project目录下
运行main.cpp,成功执行并且按照我们的预期打开了calc.exe!